﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace TripleCComiC
{
    public partial class Form1
    {
        /// <summary>
        /// Load user setting which saved from last using this viewer.
        /// </summary>
        private void LoadSetting()
        {
            if (!Properties.Settings.Default.FirstLaunch)
            {
                var userDef = Properties.Settings.Default;

                uiSetting.LoadProperties();
                if (userDef.Font!= null)
                {
                    SetFont(userDef.Font);
                }
                switch((SizeMode)userDef.Mode)
                {
                    case SizeMode.FitHeigth:
                        FitHeight_Click(null, null);
                        break;
                    case SizeMode.FitWidth:
                        FitWidth_Click(null, null);
                        break;
                    case SizeMode.Original:
                        Original_Click(null, null);
                        break;
                }
                uiSetting.SelectedSortOption =
                    (UISetting.SortOption)userDef.SortOption;
                uiSetting.FullScreenChecked = userDef.Fullscreen;
                if(this.WindowState == FormWindowState.Normal && userDef.Fullscreen == true)
                {
                    FullScreen_Click(null, null);
                }
                // _ratioZoom should be changed after size mode be changed.
                _ratioZoom = userDef.ZoomRatio;
            }
            else
            {
                Properties.Settings.Default.FirstLaunch = false;
            }
        }

        /// <summary>
        /// Save user setting before program closed.
        /// </summary>
        private void SaveSetting()
        {
            var userDef = Properties.Settings.Default;

            uiSetting.SaveProperties();
            userDef.Font = this.Font;
            userDef.Mode = (int)_sizeMode;
            userDef.ZoomRatio = _ratioZoom;

            Properties.Settings.Default.Save();
        }

        /// <summary>
        /// Set gobal font.
        /// </summary>
        /// <param name="font">Font.</param>
        private void SetFont(Font font)
        {
            this.Font = font;
            uiSetting.FontInformation =
                Txt("fontName", "Font Name: ") + this.Font.FontFamily.Name +
                "\n" + Txt("fontSize", "Font Size: ") + this.Font.Size.ToString();
            foreach (Control ctrl in this.Controls)
            {
                ctrl.Font = this.Font;
            }
            uiSetting.GlobalFont = this.Font;
            Form1_SizeChanged(null, null);
        }

        /// <summary>
        /// Move current folder to specific folder.
        /// </summary>
        /// <param name="path">Specific folder's full path.</param>
        public void LoadFolder(string path)
        {
            _currentPath = path;
            _files = SortFileName(GetFiles(_currentPath));
            ClearCache();
            this.Text = _currentPath;
            LoadImage(ImageNumNow = 0);
            lbl_Status.Text = _currentPath;
        }

        /// <summary>
        /// Move current folder to specific file's folder and show selected file.
        /// </summary>
        /// <param name="path">Specific file's full path.</param>
        public void LoadFile(string fileName)
        {
            int selectedImage = 0;

            _currentPath = Path.GetDirectoryName(fileName);
            _files = SortFileName(GetFiles(_currentPath));
            selectedImage = Array.FindIndex<string>(
                _files,
                (file => file == Path.GetFullPath(fileName)));
            ImageNumNow = (selectedImage >= 0) ? selectedImage : 0;
            ClearCache();
            this.Text = _currentPath;
            LoadImage(ImageNumNow);
        }

        /// <summary>
        /// Get all image files' path.
        /// </summary>
        /// <param name="dir">Directory of image files.</param>
        /// <returns>Image files' path.</returns>
        private string[] GetFiles(string dir)
        {
            try
            {
                if (dir != String.Empty)
                {
                    IEnumerable<string> iFiles = from file in
                                                     Directory.EnumerateFiles(dir)
                                                 where (file.ToLower().Contains(".jpg")
                                                 || file.ToLower().Contains(".gif")
                                                 || file.ToLower().Contains(".jpeg")
                                                 || file.ToLower().Contains(".png")
                                                 || file.ToLower().Contains(".tiff")
                                                 || file.ToLower().Contains(".bmp"))
                                                 select file;

                    return iFiles.ToArray();
                }
            }
            catch
            {
                throw;
            }
            throw new ArgumentException();
        }

        /// <summary>
        /// Load image form Cache.
        /// If image to be load is not in cache, load image into cache.
        /// </summary>
        /// <param name="imageNum">Number of image in file list.</param>
        /// <returns></returns>
        private Image LoadFromCache(int imageNum)
        {
            if (!_imageCache.ContainsKey(imageNum))
            {
                _imageCache.Add(imageNum, ImageFromFile(_files[imageNum]));
            }
            return _imageCache[imageNum];
        }

        /// <summary>
        /// Load image to memory.
        /// </summary>
        /// <param name="filePath">Image path.</param>
        /// <returns>Image data.</returns>
        private Image ImageFromFile(string filePath)
        {
            if (!File.Exists(filePath))
            {
                return null;
            }

            using (FileStream fileStream = new FileStream(filePath, FileMode.Open))
            {
                byte[] bytes = new byte[fileStream.Length];
                fileStream.Read(bytes, 0, bytes.Length);

                MemoryStream memoryStream = new MemoryStream(bytes);
                if (memoryStream != null)
                {
                    return Image.FromStream(memoryStream);
                }
            }

            return null;
        }

        /// <summary>
        /// Clear image cache.
        /// </summary>
        private void ClearCache()
        {
            pic_Main.Image = null;
            pic_Sub.Image = null;
            while (_imageCache.Count > 0)
            {
                int key = _imageCache.Keys.Last();
                Image disposeImage = _imageCache[key];
                _imageCache.Remove(key);
                disposeImage.Dispose();
            }
        }

        /// <summary>
        /// Load and show image.
        /// </summary>
        /// <param name="imageNum">Image number.</param>
        private void LoadImage(int imageNum)
        {
            timer_Cache.Stop();
            if (_files.Length > imageNum && imageNum >= 0)
            {
                string file = _files[imageNum];
                int spaceX = 0;
                int spaceY = 0;
                int height;
                int width;
                int clientHeight;
                int clientWidth;

                clientHeight = panel_Image.ClientSize.Height;
                clientWidth = panel_Image.ClientSize.Width;

                pic_Main.SuspendLayout();
                try
                {
                    //if (pictureBox1.Image != null)
                    //{
                    //    pictureBox1.Image.Dispose();
                    //}
                    pic_Main.Image = LoadFromCache(imageNum);
                    height = pic_Main.Image.Height;
                    width = pic_Main.Image.Width;
                    switch (_sizeMode)
                    {
                        case SizeMode.FitHeigth:
                            pic_Main.Size = new Size(
                                Convert.ToInt32(_ratioZoom * width * clientHeight / height),
                                Convert.ToInt32(_ratioZoom * clientHeight));
                            break;
                        case SizeMode.FitWidth:
                            pic_Main.Size = new Size(
                                Convert.ToInt32(_ratioZoom * clientWidth),
                                Convert.ToInt32(_ratioZoom * height * clientWidth / width));
                            break;
                        case SizeMode.Original:
                            pic_Main.Size = new Size(Convert.ToInt32(_ratioZoom * pic_Main.Image.Width), Convert.ToInt32(_ratioZoom * pic_Main.Image.Height));
                            break;
                    }
                    spaceX = (clientWidth - pic_Main.Size.Width) / 2;
                    spaceY = (clientHeight - pic_Main.Size.Height) / 2;
                    this.Text = file;
                    if (pic_Sub.Visible)
                    {
                        pic_Sub.SuspendLayout();
                        try
                        {
                            pic_Sub.Image = LoadFromCache(
                                (_rightFlip) ? imageNum - 1 : imageNum + 1);
                            pic_Sub.Size = pic_Main.Size;
                            spaceX = (clientWidth - pic_Main.Size.Width - pic_Sub.Size.Width) / 2;
                        }
                        catch
                        {
                            pic_Sub.Image = pic_Sub.ErrorImage;
                            pic_Sub.Size = new Size(0, 0);
                        }
                        finally
                        {
                            pic_Sub.ResumeLayout();
                        }
                    }
                }
                catch
                {
                    pic_Main.Image = pic_Main.ErrorImage;
                    this.Text = Txt("AppName", "TripleC ComiX Viewer");
                }
                finally
                {
                    int x = pic_Main.Location.X;
                    int y = pic_Main.Location.Y;
                    pic_Main.Location =
                        new Point((spaceX > 0) ? spaceX : 0, (spaceY > 0) ? spaceY : 0);
                    pic_Sub.Location = new Point(pic_Main.Right, pic_Main.Top);
                    pic_Main.ResumeLayout();
                    _imageTop = clientHeight - pic_Main.Height;
                    _imageLeft = clientWidth - pic_Main.Width;
                    if (pic_Sub.Visible)
                    {
                        _imageLeft -= pic_Sub.Width;
                    }
                    pic_BarFront.Size = new Size((_files.Length > 0) ? pic_BarBack.Width * (_imageNumNow + 1) / _files.Length : 0, uiSetting.BarHeight);
                    if (!uiSetting.HomingChecked)
                    {
                        if (x < 0)
                        {
                            ImageRight(-x);
                        }
                        if (y < 0)
                        {
                            ImageDown(-y);
                        }
                    }
                }
                timer_Cache.Start();
            }
        }

        /// <summary>
        /// Set distance between edge of client and image.
        /// </summary>
        private void SetDistance()
        {
            _distanceX = Convert.ToInt32(panel_Image.ClientSize.Width * _ratioX);
            _distanceY = Convert.ToInt32(panel_Image.ClientSize.Height * _ratioY);
        }

        /// <summary>
        /// Sort file names.
        /// </summary>
        /// <param name="unsortArray">Unsorted file names.</param>
        /// <returns>Sorted file names.</returns>
        public string[] SortFileName(string[] unsortArray)
        {
            if (uiSetting.radio_NotSort.Checked)
            {
                return unsortArray;
            }
            else if (uiSetting.radio_SystemDef.Checked)
            {
                string[] sorted = new string[unsortArray.Length];

                for (int insertPoint = 0; insertPoint < unsortArray.Length; insertPoint++)
                {
                    sorted[insertPoint] = unsortArray[insertPoint];
                    for (int before = insertPoint - 1; before >= 0; before--)
                    {
                        if (String.Compare(sorted[before], sorted[before + 1]) > 0)
                        {
                            string sTemp = sorted[before];
                            sorted[before] = sorted[before + 1];
                            sorted[before + 1] = sTemp;
                        }
                        else
                        {
                            break;
                        }
                    }
                }

                return sorted;
            }
            else
            {
                string[] sorted = new string[unsortArray.Length];
                //_fileColors = new Color[unsortArray.Length];
                int completeIndex = 0;
                Dictionary<string, Queue<string>> classifiedArray = new Dictionary<string, Queue<string>>();

                foreach (string filename in unsortArray)
                {
                    string mask = Regex.Replace(Path.GetFileName(filename), @"\d+", "*");
                    int headerLength =
                        (uiSetting.radio_ByFirstNum.Checked) ? mask.IndexOf('*') : mask.LastIndexOf('*');
                    string headerStr = (headerLength >= 0) ? mask.Substring(0, headerLength + 1) : mask;

                    if (!classifiedArray.ContainsKey(headerStr))
                    {
                        classifiedArray.Add(headerStr, new Queue<string>());
                    }
                    classifiedArray[headerStr].Enqueue(filename);
                }
                foreach (Queue<string> value in classifiedArray.Values)
                {
                    string[] sortedSquad = SortByNumber(value.ToArray());
                    sortedSquad.CopyTo(sorted, completeIndex);
                    /*
                    for (int i = completeIndex; i < completeIndex + sortedSquad.Length; i++)
                    {
                        _fileColors[i] = Color.FromArgb(Int32.MaxValue / (completeIndex + 1) / 11);
                    }
                    */
                    completeIndex += sortedSquad.Length;
                }

                return sorted;
            }
        }

        /// <summary>
        /// Sort file names by number(s) in filename.
        /// </summary>
        /// <remarks>Using insertion sort.</remarks>
        /// <param name="unsortArray">Unsorted file names.</param>
        /// <returns>Sorted file names.</returns>
        private string[] SortByNumber(string[] unsortArray)
        {
            string[] sorted = new string[unsortArray.Length];
            bool[] hasNum = new bool[unsortArray.Length]; // If a file didn't has number, then ignore it.
            int[][] num = new int[unsortArray.Length][]; // num[File#][Numbers in filename]

            for (int insertPoint = 0; insertPoint < unsortArray.Length; insertPoint++)
            { // Foreach unsort file name.
                MatchCollection match =
                    Regex.Matches(Path.GetFileNameWithoutExtension(unsortArray[insertPoint]), @"\d+");

                // Get numbers.
                if (match.Count > 0)
                {
                    num[insertPoint] = new int[match.Count];
                    for (int nthNum = 0; nthNum < match.Count; nthNum++)
                    {
                        int length = match[nthNum].Value.Length;
                        num[insertPoint][nthNum] = Convert.ToInt32((length > 9) ?
                            match[nthNum].Value.Substring(length - 9) :
                            match[nthNum].Value); // Only compare last 9 order.
                    }
                    hasNum[insertPoint] = true;
                }
                else
                {
                    hasNum[insertPoint] = false;
                }

                // Insert
                sorted[insertPoint] = unsortArray[insertPoint];
                if (hasNum[insertPoint])
                {
                    for (int before = insertPoint - 1; before >= 0; before--)
                    {
                        bool isComplete = true; // Assume file is insert into right position.
                        if (hasNum[before])
                        {
                            int minLength = Math.Min(num[before + 1].Length, num[before].Length);
                            for (int nthNum = 0; nthNum < minLength; nthNum++)
                            {
                                if (num[before][nthNum] > num[before + 1][nthNum])
                                { // If true, files needs switch position.
                                    int[] nTemp = num[before];
                                    string sTemp = sorted[before];

                                    num[before] = num[before + 1];
                                    sorted[before] = sorted[before + 1];
                                    num[before + 1] = nTemp;
                                    sorted[before + 1] = sTemp;
                                    isComplete = false;
                                    // To compare the file before this file.
                                    break;
                                }
                                else if (num[before][nthNum] < num[before + 1][nthNum])
                                {
                                    // File is at right position.
                                    break;
                                }
                                // else: To compare next number in string.
                            }
                            if (isComplete)  // If true, file is at right position.
                            {
                                // To next string.
                                break;
                            }
                        }
                        else
                        {
                            // To next string.
                            break;
                        }
                    }
                }
            }

            return sorted;
        }

        /// <summary>
        /// Change to full screen mode.
        /// </summary>
        private void FullScreen()
        {
            if (this.WindowState == FormWindowState.Normal)
            {
                this.FormBorderStyle = FormBorderStyle.None;
                this.TopMost = true;
                toolStrip1.Visible = false;
                toolStrip_PlayCtrlBar.Visible = false;
                this.WindowState = FormWindowState.Maximized;
                LoadImage(ImageNumNow);
            }
            else
            {
                this.FormBorderStyle = FormBorderStyle.Sizable;
                this.TopMost = false;
                toolStrip1.Visible = true;
                toolStrip_PlayCtrlBar.Visible = true;
                this.WindowState = FormWindowState.Normal;
                LoadImage(ImageNumNow);
            }
        }

        /// <summary>
        /// Move image up.
        /// </summary>
        /// <param name="distance">Move distance.</param>
        private void ImageUp(int distance)
        {
            if (pic_Main.Height > ClientSize.Height)
            {
                if (pic_Main.Location.Y < -distance)
                {
                    pic_Main.Location =
                        new Point(pic_Main.Location.X, pic_Main.Location.Y + distance);
                }
                else if (pic_Main.Location.Y < 0)
                {
                    pic_Main.Location =
                        new Point(pic_Main.Location.X, 0);
                }
                pic_Sub.Location =
                    new Point(pic_Main.Right, pic_Main.Top);
            }
        }

        /// <summary>
        /// Move image down.
        /// </summary>
        /// <param name="distance">Move distance.</param>
        private void ImageDown(int distance)
        {
            if (pic_Main.Height > ClientSize.Height)
            {
                if (pic_Main.Location.Y > _imageTop + distance)
                {
                    pic_Main.Location =
                        new Point(pic_Main.Location.X, pic_Main.Location.Y - distance);
                }
                else if (pic_Main.Location.Y > _imageTop)
                {
                    pic_Main.Location =
                        new Point(pic_Main.Location.X, _imageTop);
                }
                pic_Sub.Location =
                    new Point(pic_Main.Right, pic_Main.Top);
            }
        }

        /// <summary>
        /// Move image left.
        /// </summary>
        /// <param name="distance">Move distance.</param>
        private void ImageLeft(int distance)
        {
            if (pic_Main.Width + ((pic_Sub.Visible) ? pic_Sub.Width : 0) > ClientSize.Width)
            {
                if (pic_Main.Location.X < -distance)
                {
                    pic_Main.Location =
                        new Point(pic_Main.Location.X + distance, pic_Main.Location.Y);
                }
                else if (pic_Main.Location.X < 0)
                {
                    pic_Main.Location =
                        new Point(0, pic_Main.Location.Y);
                }
                pic_Sub.Location =
                    new Point(pic_Main.Right, pic_Main.Top);
            }
        }

        /// <summary>
        /// Move image right.
        /// </summary>
        /// <param name="distance">Move distance.</param>
        private void ImageRight(int distance)
        {
            if (pic_Main.Width + ((pic_Sub.Visible) ? pic_Sub.Width : 0) > ClientSize.Width)
            {
                if (pic_Main.Location.X > _imageLeft + distance)
                {
                    pic_Main.Location =
                        new Point(pic_Main.Location.X - distance, pic_Main.Location.Y);
                }
                else if (pic_Main.Location.X > _imageLeft)
                {
                    pic_Main.Location =
                        new Point(_imageLeft, pic_Main.Location.Y);
                }
                pic_Sub.Location =
                    new Point(pic_Main.Right, pic_Main.Top);
            }
        }

        /// <summary>
        /// Move image to horizontal middle.
        /// </summary>
        private void ImageXMiddle()
        {
            int step = pic_Main.Left + (pic_Main.Width - ClientSize.Width) / 2;
            if (step > 0)
            {
                ImageRight(step);
            }
            else
            {
                ImageLeft(-step);
            }
        }

        /// <summary>
        /// Move image to vertical middle.
        /// </summary>
        private void ImageYMiddle()
        {
            int step = pic_Main.Top + (pic_Main.Height - ClientSize.Height) / 2;
            if (step > 0)
            {
                ImageDown(step);
            }
            else
            {
                ImageUp(-step);
            }
        }

        /// <summary>
        /// Move images by mouse.
        /// </summary>
        /// <param name="x">X movement of mouse.</param>
        /// <param name="y">Y movement of mouse.</param>
        private void ImageDrag(int x, int y)
        {
            if (x > 0)
            {
                ImageLeft(x);
            }
            else
            {
                ImageRight(-x);
            }

            if (y > 0)
            {
                ImageUp(y);
            }
            else
            {
                ImageDown(-y);
            }
        }

        /// <summary>
        /// Set location and size of time line bar.
        /// </summary>
        private void SetBarLocSize()
        {
            pic_BarBack.Location = new Point(20, panel_Image.Height - 90);
            pic_BarBack.Size = new Size(panel_Image.Width - 40, uiSetting.BarHeight);
            pic_BarFront.Location = pic_BarBack.Location;
            pic_BarFront.Size = new Size(((_files.Length > 0) ? pic_BarBack.Width * (_imageNumNow + 1) / _files.Length : 0), uiSetting.BarHeight);
        }
        
        /// <summary>
        /// Show file list.
        /// </summary>
        private void ShowFileList()
        {
            Form form_List = new Form();
            DataGridView view_List = new DataGridView();

            form_List.Size = new Size(640, 480);
            form_List.Text = Txt("dialog_List", "File List");
            form_List.Controls.Add(view_List);
            form_List.Icon = this.Icon;
            form_List.TopMost = true;
            view_List.ScrollBars = ScrollBars.Both;
            view_List.Columns.Add("Path", "Path");
            view_List.Dock = DockStyle.Fill;
            view_List.Columns["Path"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            view_List.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            view_List.AllowUserToAddRows = false;
            view_List.AllowUserToDeleteRows = false;
            view_List.ReadOnly = true;
            //foreach (string filename in _files)
            for (int i = 0; i < _files.Length; i++)
            {
                view_List.Rows.Add(_files[i]);
                //view_List.Rows[view_List.RowCount - 1].DefaultCellStyle.ForeColor = _fileColors[i];
            }
            view_List.SelectionChanged += new EventHandler((object eventSender, EventArgs eventArgs) =>
            {
                if (view_List != null)
                {
                    if (view_List.Rows.Count > ImageNumNow &&
                        view_List.CurrentRow.Index != ImageNumNow)
                    {
                        view_List.CurrentRow.Selected = false;
                        view_List.Rows[ImageNumNow].Selected = true;
                    }
                }
            });
            view_List.DoubleClick += new EventHandler((object eventSender, EventArgs eventArgs) =>
            {
                if (view_List != null)
                {
                    Point loc = (eventArgs as MouseEventArgs).Location;
                    LoadImage(ImageNumNow = view_List.HitTest(loc.X, loc.Y).RowIndex);
                    view_List.ClearSelection();
                    view_List.Rows[ImageNumNow].Selected = true;
                }
            });
            form_List.FormClosed += new FormClosedEventHandler((object closedSender, FormClosedEventArgs closedArgs) =>
            {
                this.Deactivate += Form1_Deactivate;
            });

            this.Deactivate -= Form1_Deactivate;
            form_List.Show();
        }

        /// <summary>
        /// Capture mouse location to decide show time bar or not. If yes, show it.
        /// </summary>
        /// <param name="screenLocMouse">Mouse location relative to screen.</param>
        /// <param name="screenRectBar">Region of time bar relative to screen.</param>
        private void AutoShowBar(Point screenLocMouse, Rectangle screenRectBar)
        {
            if (screenRectBar.IntersectsWith(new Rectangle(screenLocMouse, new Size(1, 1))))
            {
                SetBarLocSize();
                pic_BarFront.Visible = true;
                pic_BarBack.Visible = true;
            }
            else if (screenLocMouse.Y > panel_Image.PointToScreen(new Point(0, panel_Image.Height)).Y - 3)
            {
                toolStrip_PlayCtrlBar.Visible = true;
                SetBarLocSize();
                pic_BarFront.Visible = true;
                pic_BarBack.Visible = true;
            }
            else
            {
                toolStrip_PlayCtrlBar.Visible = false;
                pic_BarFront.Visible = false;
                pic_BarBack.Visible = false;
            }

        }

        /// <summary>
        /// Caculate position of time line bar coresponded with time.
        /// </summary>
        /// <param name="time">Time.</param>
        /// <returns>Position of time line bar.</returns>
        private int ToBarPos(double time)
        {
            return (_files.Length > 0) ?
                Convert.ToInt32(time * pic_BarBack.Width / _files.Length / Convert.ToInt32(1000 / uiSetting.FPS)) :
                0;
        }

        /// <summary>
        /// Caculate image number coresponded with time.
        /// </summary>
        /// <param name="time">Time.</param>
        /// <returns>Image number.</returns>
        private int ToImageNum(double time)
        {
            return Convert.ToInt32(time / Convert.ToInt32(1000 / uiSetting.FPS));
        }

        /// <summary>
        /// Load text from language package.
        /// </summary>
        /// <param name="name">Text name.</param>
        /// <param name="instead">If text not exist, return this text.</param>
        /// <returns>Text from language package.</returns>
        public string Txt(string name, string instead)
        {
            if (LangDict.ContainsKey(name))
            {
                return LangDict[name];
            }
            else
            {
                return instead;
            }
        }
    }
}
